﻿/* cpcPaChi;

   Un compilador de Pascal chiquitito para CPC
   Por Nacho Cabanes - Version en C#

   Clase "Parser" (analizador sintático)

   Versiones hasta la fecha:

   Num.    Fecha       Cambios
   ---------------------------------------------------
   0.15cs  01-Dic-2010  Añade las órdenes CPCINK y WRITESTRING
   0.14cs  17-Nov-2010  Permite definir procedimientos
                        (sin parametros ni variables locales).
   0.13cs  14-Nov-2010  Permite bloques WHILE, REPEAT, FOR
                        Permite DEC ademas de INC
                        Puede calcular la comparación inversa de otra
   0.12cs  10-Nov-2010  Permite bloques IF..THEN..ELSE
                        AnalizarCualquierOrden no obtiene la orden, por
                          versatilidad.
                        Anadido AnalizarBloqueBEGIN_END
   0.11cs  08-Nov-2010  AnalizarPrograma reconoce LABEL
                        AnalizarCualquierOrden reconoce GOTO y etiquetas
   0.10cs  06-Nov-2010  Se puede incrementar una variable con INC
   0.09cs  04-Nov-2010  Se puede declarar variables con VAR
                        y dar valor con :=
   0.07cs  31-Oct-2010  Permite definir constantes (añade AnalizarConst
                        y usa "obtenerEnteroOIdentificador")
   0.06cs  29-Oct-2010  Incluye las ordenes PEN, PAPER, CPCMODE
   0.05cs  27-Oct-2010  Primera version del analizador sintactico,
                        extraido del programa principal.
*/

using System;
using System.IO;

public class Parser
{
  Scanner miScanner;
  TablaSimbolos miTablaSimb;
  CodeGen miGenerador;
  int numeroIFactual = 1;
  bool analisisTerminado = false;
  bool depurando = false;

  /* Constructor: enlaza con el analizador lexico */
  public Parser(Scanner analizadorLexico, CodeGen generador,
    TablaSimbolos tabla)
  {
    miScanner = analizadorLexico;
    miTablaSimb = tabla;
    miGenerador = generador;
  }



  /* Estructura de un programa:
   program <identificador>;
   begin
     <ordenes>
   end.
   */

  /* Analisis sintatico de toda la estructura del programa */
  public void AnalizarPrograma()
  {
    /* program <identificador>; */
    string orden = miScanner.ObtenerIdentificador().ToUpper();
    if ( orden != "PROGRAM" )
      miScanner.ErrorFatal("No se ha encontrado PROGRAM");
    orden = miScanner.ObtenerIdentificador(); // Nombre de programa
    miScanner.LeerSimbolo(';');

    /* const, var, label o procedure hasta llegar a begin */
    do {
      orden = miScanner.ObtenerIdentificador().ToUpper();

      if ( orden == "CONST" )
        analizarCONST();
      else if ( orden == "VAR" )
        analizarVAR();
      else if ( orden == "LABEL" )
        analizarLABEL();
      else if ( orden == "PROCEDURE" )
        analizarPROCEDURE();
      else if ( orden != "BEGIN" )
        miScanner.ErrorFatal("No se ha encontrado BEGIN");
    }
    while ( orden != "BEGIN" );

    miGenerador.genComentario("-- Cuerpo del programa");
    miGenerador.genLABEL("INICIOPROGRAMA");

    /* Resto de ordenes */
    do
    {
      orden = miScanner.ObtenerIdentificador().ToUpper();
      analizarCualquierOrden( orden );
    }
    while ( ! analisisTerminado );
  }


  /* Analiza una orden simple
   o compuesta -bloque BEGIN...END- */
  void analizarBloqueBEGIN_END()
  {
    string orden = miScanner.ObtenerIdentificador().ToUpper();
    // Si no es BEGIN, es sentencia simple
    if (orden != "BEGIN")
      analizarCualquierOrden( orden );
    else
    {
      do
      {
        // Si habia BEGIN, debe terminar con END
        orden = miScanner.ObtenerIdentificador().ToUpper();
        if (orden != "END")
          analizarCualquierOrden( orden );
      }
      while (orden != "END");
      miScanner.LeerSimbolo(';');
    }

  }


  /* Espera cualquiera de las ordenes reconocidas y genera codigo
   para ella.
   Por ahora permite:
   - Genericas: CLS, LOCATE, WRITECHAR
   - Especificas de CPC: CPCMODE, CPCPAPER, CPCPEN
  */
  void analizarCualquierOrden(string orden)
  {
    //string orden = miScanner.ObtenerIdentificador().ToUpper();

    if (depurando)
      Console.WriteLine("Analizando: {0}", orden);

    if (orden == null)
    {
      analisisTerminado = true;
      return;
    }

    switch (orden)
    {
      case "CLS": analizarCLS(); break;
      case "CPCINK": analizarCPCINK(); break;
      case "CPCMODE": analizarCPCMODE(); break;
      case "DEC": analizarDEC(); break;
      case "FOR": analizarFOR(); break;
      case "GOTO": analizarGOTO(); break;
      case "IF": analizarIF(); break;
      case "INC": analizarINC(); break;
      case "LOCATE": analizarLOCATE(); break;
      case "PAPER": analizarPAPER(); break;
      case "PEN": analizarPEN(); break;
      case "PROCEDURE": analizarPROCEDURE(); break;
      case "REPEAT": analizarREPEAT(); break;
      case "WHILE": analizarWHILE(); break;
      case "WRITECHAR": analizarWRITECHAR(); break;
      case "WRITESTRING": analizarWRITESTRING(); break;

      case "END":
        miScanner.LeerSimbolo('.');
        analisisTerminado = true;
        break;
      case "":
        break;
      default:
        if (miTablaSimb.LeerTipo(orden) == tipoId.INEXIST)
          miScanner.ErrorFatal("Error: orden no reconocida: -"+orden+"-");
        analizarLABELoASIGN(orden);
        break;
    }
  }


  void analizarLABELoASIGN( string identif )
  {
    //   ## Provisional: no deberia permitir espacio entre : y =

    char siguiente1 = miScanner.LeerCualquierSimbolo();

    if (siguiente1 == ';')  // nombre; => procedimiento
    {
      miGenerador.genComentario(" Llamada a procedimiento: "+identif);
      miGenerador.genLLamada();
      miGenerador.genDireccSinGOTO(identif);
      return;
    }
    miScanner.Retroceder();

    // Si es : puede ser Asign o Label
    miScanner.LeerSimbolo(':');
    char siguiente2 = miScanner.LeerCualquierSimbolo();
    if (siguiente2 == '=')  // := => Asignacion
    {
      string valor = miScanner.ObtenerEnteroOIdentificador().ToUpper();
      miScanner.LeerSimbolo(';');
      miGenerador.genASIGN(identif,valor);
    }
    else
    {     // : => Etiqueta
      miGenerador.genLABEL(identif);
      miScanner.Retroceder();  // He leido un símbolo de más
    }
  }


  void analizarCLS()
  {
    miScanner.LeerSimbolo(';');
    miGenerador.genCLS();
  }

  void analizarCONST()
  {
    string identificador = miScanner.ObtenerIdentificador();
    miScanner.LeerSimbolo('=');
    int valor = miScanner.ObtenerEntero();
    miTablaSimb.Insertar(identificador, tipoId.CONST,
      tamDato.BYTE, valor );
    char terminador = miScanner.LeerCualquierSimbolo();
    if ( (terminador != ';') && (terminador != ',') )
      miScanner.ErrorFatal("Terminador incorrecto: {0}" + terminador);

    if (terminador == ',')
      analizarCONST();
  }


  void analizarCPCINK()
  {
    miScanner.LeerSimbolo('(');
    string tinta = miScanner.ObtenerEnteroOIdentificador();
    miScanner.LeerSimbolo(',');

    string color1 = miScanner.ObtenerEnteroOIdentificador();
    char separador = miScanner.LeerCualquierSimbolo();
    string color2;
    if (separador == ',')
    {
      color2 = miScanner.ObtenerEnteroOIdentificador();
    }
    else
    {
      color2 = color1;
      miScanner.Retroceder();
    }
    miScanner.LeerSimbolo(')');
    miScanner.LeerSimbolo(';');
    miGenerador.genCPCINK(tinta, color1, color2);
  }


  void analizarCPCMODE()
  {
    miScanner.LeerSimbolo('(');
    miGenerador.GenLecturaValor( miScanner.ObtenerEnteroOIdentificador() );
    miScanner.LeerSimbolo(')');
    miScanner.LeerSimbolo(';');
    miGenerador.genCPCMODE();
  }

  void analizarDEC()
  {
    miScanner.LeerSimbolo('(');
    string identif = miScanner.ObtenerIdentificador();
    miScanner.LeerSimbolo(')');
    miScanner.LeerSimbolo(';');
    miGenerador.genDEC( identif, 1 );
  }

  void analizarFOR()
  {
    string ident, ordenTo;
    string valorIni, valorFin;
    string ifActual;

    /* Uso el numero de IF actual para generar
       las etiquetas de salto. Lo copio en una variable
       local para evitar modificaciones inesperadas
       en IF anidados */
    ifActual = Convert.ToString(numeroIFactual);
    numeroIFactual ++;
    miTablaSimb.Insertar("FOR_"+ifActual, tipoId.LABEL,
      tamDato.INTEGER, 0 );
    miTablaSimb.Insertar("FIN_FOR_"+ifActual, tipoId.LABEL,
      tamDato.INTEGER, 0 );
    // Comienza el analisis en si
    ident = miScanner.ObtenerIdentificador().ToUpper();
    miScanner.LeerSimbolo(':'); //  ### No debería permitir espacio aquí
    miScanner.LeerSimbolo('=');

    valorIni = miScanner.ObtenerEnteroOIdentificador().ToUpper();
    miGenerador.genASIGN(ident, valorIni);
    miGenerador.genLABEL("FOR_"+ifActual);
    ordenTo = miScanner.ObtenerIdentificador().ToUpper();
    if (ordenTo != "TO") miScanner.ErrorFatal("FOR sin TO");
    valorFin = miScanner.ObtenerEnteroOIdentificador().ToUpper();

    ordenTo = miScanner.ObtenerIdentificador().ToUpper();
    if (ordenTo != "DO") miScanner.ErrorFatal("FOR sin DO");

    miGenerador.genIF(valorFin, "<", ident);
    miGenerador.genDireccSinGOTO("FIN_FOR_"+ifActual);

    // Bloque de codigo tras FOR
    analizarBloqueBEGIN_END();
    miGenerador.genINC(ident, 1);
    miGenerador.genGOTO("FOR_"+ifActual);
    miGenerador.genLABEL("FIN_FOR_"+ifActual);
    //miScanner.LeerSimbolo(';');
  }



  void analizarGOTO()
  {
    string identif = miScanner.ObtenerIdentificador();
    miScanner.LeerSimbolo(';');
    miGenerador.genGOTO( identif );
  }

  void analizarIF()
  {
    string ident1, ident2, ordenThen, ordenElse;
    string tipoComparacion;
    char terminador;
    string ifActual;

    /* Uso el numero de IF actual para generar
       las etiquetas de salto. Lo copio en una variable
       local para evitar modificaciones inesperadas
       en IF anidados */
    ifActual = Convert.ToString(numeroIFactual);
    numeroIFactual ++;
    miTablaSimb.Insertar("ELSE_"+ifActual, tipoId.LABEL,
      tamDato.INTEGER, 0 );
    miTablaSimb.Insertar("FIN_IF_"+ifActual, tipoId.LABEL,
      tamDato.INTEGER, 0 );
    // Comienza el analisis en si
    ident1 = miScanner.ObtenerIdentificador().ToUpper();
    tipoComparacion = ""+miScanner.LeerCualquierSimbolo(); // ### A mejorar
    ident2 = miScanner.ObtenerEnteroOIdentificador().ToUpper();

    ordenThen = miScanner.ObtenerIdentificador().ToUpper();
    if (ordenThen != "THEN") miScanner.ErrorFatal("IF sin THEN");
    miGenerador.genIF(ident1,
      inversoComparacion(tipoComparacion), ident2);
    miGenerador.genDireccSinGOTO("ELSE_"+ifActual);
    // Bloque de codigo tras IF-THEN
    analizarBloqueBEGIN_END();
    miGenerador.genGOTO("FIN_IF_"+ifActual);
    miGenerador.genLABEL("ELSE_"+ifActual);
    ordenElse = miScanner.ObtenerIdentificador().ToUpper();
    if (ordenElse == "ELSE")
      {
      analizarBloqueBEGIN_END();
      terminador = miScanner.LeerCualquierSimbolo();
      }
    else
    {
      string orden = miScanner.ObtenerIdentificador().ToUpper();
      analizarCualquierOrden( orden );
    }
    /* Si no, es ; o error */
    /*if terminador <> ';' then
      errorFatal('Sin ; al final de IF');*/
    miGenerador.genLABEL("FIN_IF_"+ifActual);
  }


  void analizarINC()
  {
    miScanner.LeerSimbolo('(');
    string identif = miScanner.ObtenerIdentificador();
    miScanner.LeerSimbolo(')');
    miScanner.LeerSimbolo(';');
    miGenerador.genINC( identif, 1 );
  }

  void analizarLABEL()
  {
    string identificador = miScanner.ObtenerIdentificador();
    miTablaSimb.Insertar(identificador, tipoId.LABEL,
      tamDato.INTEGER, 0 );
    char terminador = miScanner.LeerCualquierSimbolo();
    if ( (terminador != ';') && (terminador != ',') )
      miScanner.ErrorFatal("Terminador incorrecto: {0}" + terminador);

    if (terminador == ',')
      analizarLABEL();
  }

  void analizarLOCATE()
  {
    miScanner.LeerSimbolo('(');
    miGenerador.GenLecturaValor( miScanner.ObtenerEnteroOIdentificador() );
    miGenerador.genLOCATE1();
    miScanner.LeerSimbolo(',');
    miGenerador.GenLecturaValor( miScanner.ObtenerEnteroOIdentificador() );
    miScanner.LeerSimbolo(')');
    miScanner.LeerSimbolo(';');
    miGenerador.genLOCATE2();
  }

  void analizarPAPER()
  {
    miScanner.LeerSimbolo('(');
    miGenerador.GenLecturaValor( miScanner.ObtenerEnteroOIdentificador() );
    miScanner.LeerSimbolo(')');
    miScanner.LeerSimbolo(';');
    miGenerador.genCPCPAPER( );
  }

  void analizarPEN()
  {
    miScanner.LeerSimbolo('(');
    miGenerador.GenLecturaValor( miScanner.ObtenerEnteroOIdentificador() );
    miScanner.LeerSimbolo(')');
    miScanner.LeerSimbolo(';');
    miGenerador.genCPCPEN();
  }

  void analizarPROCEDURE()
  {
    // ...................
    /*miScanner.LeerSimbolo('(');
    miGenerador.GenLecturaValor( miScanner.ObtenerEnteroOIdentificador() );
    miScanner.LeerSimbolo(')');
    miScanner.LeerSimbolo(';');
    miGenerador.genCPCPAPER( );*/
    // ...................

    // Primero el nombre del procedimiento
    string identif = miScanner.ObtenerIdentificador().ToUpper();

    miTablaSimb.Insertar(identif, tipoId.PROCEDURE,
      tamDato.INTEGER, miGenerador.GetDireccionActual() );
    miScanner.LeerSimbolo(';');
    miGenerador.genComentario("- Procedure " + identif);

    string orden = miScanner.ObtenerIdentificador().ToUpper();
    // Luego un bloque BEGIN...END *)
    if (orden != "BEGIN") miScanner.ErrorFatal("PROCEDURE sin BEGIN");
    do {
        // Debe terminar con END
        orden = miScanner.ObtenerIdentificador().ToUpper();
          if (orden != "END")
            analizarCualquierOrden(orden);
      }
      while (orden != "END");
      miGenerador.genRetorno();
      miScanner.LeerSimbolo(';');
  }


  void analizarREPEAT()
  {
    string ident1, ident2, ordenRepeat;
    string tipoComparacion;
    string ifActual;

    /* Uso el numero de IF actual para generar
       las etiquetas de salto. Lo copio en una variable
       local para evitar modificaciones inesperadas
       en IF anidados */
    ifActual = Convert.ToString(numeroIFactual);
    numeroIFactual ++;
    miTablaSimb.Insertar("REPEAT_"+ifActual, tipoId.LABEL,
      tamDato.INTEGER, 0 );
    miGenerador.genLABEL("REPEAT_"+ifActual);
    // Comienza el analisis en si
    do
    {
      ordenRepeat = miScanner.ObtenerIdentificador().ToUpper();
      if (ordenRepeat != "UNTIL")
        analizarCualquierOrden( ordenRepeat );
    }
    while (ordenRepeat != "UNTIL");

    ident1 = miScanner.ObtenerIdentificador().ToUpper();
    tipoComparacion = ""+miScanner.LeerCualquierSimbolo();  // ### A mejorar
    ident2 = miScanner.ObtenerEnteroOIdentificador().ToUpper();
    miGenerador.genComentario("REPEAT UNTIL "+ident1+tipoComparacion+ident2);

    miGenerador.genIF(ident1,
      inversoComparacion(tipoComparacion), ident2);
    miGenerador.genDireccSinGOTO("REPEAT_"+ifActual);
    miScanner.LeerSimbolo(';');
  }


  void analizarVAR()
  {
    string identificador = miScanner.ObtenerIdentificador();

    char separador = miScanner.LeerCualquierSimbolo();
    while (separador == ',') //Si quedan variables
    {
      miTablaSimb.Insertar(identificador, tipoId.VAR,
        tamDato.BYTE, 0 ); // Dentro de poco,
        // habrá que comprobar el tipo antes
      identificador = miScanner.ObtenerIdentificador().ToUpper();
      separador = miScanner.LeerCualquierSimbolo();
    }
    if (separador == ':') // Fin de declaracion
    {
      string tipoVariable = miScanner.ObtenerIdentificador();
      if ( tipoVariable != "BYTE" )
        miScanner.ErrorFatal("Tipo de datos incorrecto");
      miTablaSimb.Insertar(identificador, tipoId.VAR,
        tamDato.BYTE, 0 );
      char terminador = miScanner.LeerCualquierSimbolo();
      // if ( (terminador != ';') && (terminador != ',') )
      if ( terminador != ';' )
        miScanner.ErrorFatal("Terminador incorrecto: {0}" + terminador);
    }
    //if (terminador == ',')
    //  analizarVAR();
  }

  void analizarWHILE()
  {
    string ident1, ident2, ordenDo;
    string tipoComparacion;
    string ifActual;

    /* Uso el numero de IF actual para generar
       las etiquetas de salto. Lo copio en una variable
       local para evitar modificaciones inesperadas
       en IF anidados */
    ifActual = Convert.ToString(numeroIFactual);
    numeroIFactual ++;
    miTablaSimb.Insertar("WHILE_"+ifActual, tipoId.LABEL,
      tamDato.INTEGER, 0 );
    miGenerador.genLABEL("WHILE_"+ifActual);
    miTablaSimb.Insertar("FIN_WHILE_"+ifActual, tipoId.LABEL,
      tamDato.INTEGER, 0 );
    // Comienza el analisis en si
    ident1 = miScanner.ObtenerIdentificador().ToUpper();
    tipoComparacion = ""+miScanner.LeerCualquierSimbolo(); // ### A mejorar
    ident2 = miScanner.ObtenerEnteroOIdentificador().ToUpper();
    miGenerador.genComentario("WHILE "+ident1+tipoComparacion+ident2);
    ordenDo = miScanner.ObtenerIdentificador().ToUpper();
    if (ordenDo != "DO") miScanner.ErrorFatal("WHILE sin DO");
    miGenerador.genIF(ident1,
      inversoComparacion(tipoComparacion), ident2);
    miGenerador.genDireccSinGOTO("FIN_WHILE_"+ifActual);
    // Bloque de codigo tras WHILE
    analizarBloqueBEGIN_END();
    miGenerador.genGOTO("WHILE_"+ifActual);
    miGenerador.genLABEL("FIN_WHILE_"+ifActual);
    miScanner.LeerSimbolo(';');
  }

  void analizarWRITECHAR()
  {
    miScanner.LeerSimbolo('(');
    miScanner.LeerSimbolo('\'');
    char letra = miScanner.ObtenerLetra();
    miScanner.LeerSimbolo('\'');
    miScanner.LeerSimbolo(')');
    miScanner.LeerSimbolo(';');
    miGenerador.genWRITECHAR( letra );
  }


  void analizarWRITESTRING()
  {
    miScanner.LeerSimbolo('(');
    miScanner.LeerSimbolo('\'');
    string texto = miScanner.ObtenerCadena();
    miScanner.LeerSimbolo('\'');
    miScanner.LeerSimbolo(')');
    miScanner.LeerSimbolo(';');
    miGenerador.genWRITESTRING( texto );
  }

  // Auxiliar, para cuando hay que "dar la vuelta" a una comparación
  string inversoComparacion(string c)
  {
    if (c == "=") return "<>"; else
    if (c == "<>") return "="; else
    if (c == ">") return "<="; else
    if (c == "<") return ">="; else
    if (c == ">=") return "<"; else
    if (c == "<=") return ">";
    else return "";
  }

}
